
// Copyright (C) 2012 Luca Piccioni
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//  
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//  
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;

namespace Derm.Xml
{
	/// <summary>
	/// Utility class for managing XML documents and serialization.
	/// </summary>
	public static class XmlHelper
	{
		#region XML Schema Locations Management

		/// <summary>
		/// Class dedicated for resolving XML resources.
		/// </summary>
		private class LocalXmlResolver : XmlUrlResolver
		{
			#region XmlUrlResolved Overrides

			public override object GetEntity(Uri absoluteUri, string role, Type ofObjectToReturn)
			{
				return (new FileStream(absoluteUri.AbsolutePath, FileMode.Open));
			}

			public override Uri ResolveUri(Uri baseUri, string relativeUri)
			{
				string schemaPath = GetXmlSchemaPath(relativeUri);

				return (new Uri(schemaPath, UriKind.Absolute));
			}

			#endregion
		}

		/// <summary>
		/// Add a search path for finding XML schemas.
		/// </summary>
		/// <param name="searchPath">
		/// A <see cref="System.String"/> indicating a search path for finding XML schemas.
		/// </param>
		/// <exception cref="ArgumentNullException">
		/// Exception thrown if <paramref name="searchPath"/> is null.
		/// </exception>
		public static void AddXmlSchemaSearchPath(string searchPath)
		{
			if (searchPath == null)
				throw new ArgumentNullException("searchPath");

			sXmlSchemasSearchPaths.Add(searchPath);
		}

		/// <summary>
		/// Remove a search path for finding XML schemas.
		/// </summary>
		/// <param name="searchPath">
		/// A <see cref="System.String"/> indicating a search path for finding XML schemas.
		/// </param>
		/// <exception cref="ArgumentNullException">
		/// Exception thrown if <paramref name="searchPath"/> is null.
		/// </exception>
		public static void RemoveXmlSchemaSearchPath(string searchPath)
		{
			if (searchPath == null)
				throw new ArgumentNullException("searchPath");

			sXmlSchemasSearchPaths.Remove(searchPath);	
		}

		/// <summary>
		/// Get a XML schema path using all registered search paths.
		/// </summary>
		/// <param name="schemaLocation">
		/// A <see cref="System.String"/> taht specify the location of the XML schema relative to all registered
		/// search paths.
		/// </param>
		/// <returns>
		/// It returns an path where the XML schema location was found. If no XML schema was found, it returns
		/// <paramref name="schemaLocation"/> as it is.
		/// </returns>
		/// <exception cref="ArgumentNullException">
		/// Exception thrown if <paramref name="schemaLocation"/> is null.
		/// </exception>
		private static string GetXmlSchemaPath(string schemaLocation)
		{
			if (schemaLocation == null)
				throw new ArgumentNullException("schemaLocation");

			// Search path: current directory
			if (File.Exists(schemaLocation))
				return (schemaLocation);

			// Search path: list of search paths
			string currentDirectory = Directory.GetCurrentDirectory();

			foreach (string searchPath in sXmlSchemasSearchPaths) {
				string processedSearchPath = ProcessXmlSchemaSearchPath(searchPath);

				// Make rooted path, if necessary
				if (Path.IsPathRooted(processedSearchPath) == false)
					processedSearchPath = Path.Combine(currentDirectory, processedSearchPath);
				// Append schema location
				processedSearchPath = Path.Combine(processedSearchPath, schemaLocation);

				if (File.Exists(processedSearchPath))
					return (processedSearchPath);
			}

			// XML schema not found: probably it goes for errors
			return (schemaLocation);
		}

		/// <summary>
		/// Process a XML schema search path.
		/// </summary>
		/// <param name="searchPath">
		/// A <see cref="System.String"/> indicating a search path for finding XML schemas.
		/// </param>
		/// <returns>
		/// It returns the processed result of <paramref name="searchPath"/>.
		/// </returns>
		private static string ProcessXmlSchemaSearchPath(string searchPath)
		{
			if (searchPath == null)
				throw new ArgumentNullException("searchPath");

			// Replace environment variables
			searchPath = sSearchPathEnvironmentVarRegex.Replace(searchPath, delegate(Match match) {
				string envVariableName = match.Value, envVariableValue;

				// Resolve environment variable
				envVariableValue = Environment.GetEnvironmentVariable(envVariableName);

				return (envVariableValue ?? String.Empty);
			});

			return (searchPath);
		}

		/// <summary>
		/// Search path for XML schemas.
		/// </summary>
		private static readonly List<string> sXmlSchemasSearchPaths = new List<string>();

		/// <summary>
		/// Regular expression for replacing environment variable in search patterns.
		/// </summary>
		private static readonly Regex sSearchPathEnvironmentVarRegex = new Regex(@"%(?<EnvVar>\w+)%");

		#endregion

		#region XML Document Validation

		/// <summary>
		/// Validate a XML document using a specific XML schema.
		/// </summary>
		/// <param name="xmlStream">
		/// A <see cref="Stream"/> that specify the XML document.
		/// </param>
		public static void ValidateDocument(Stream xmlStream)
		{
			if (xmlStream == null)
				throw new ArgumentNullException("xmlStream");

			// Validate XML document
			XmlReaderSettings settings = new XmlReaderSettings();
			
			settings.ValidationFlags =
				XmlSchemaValidationFlags.ReportValidationWarnings |
				XmlSchemaValidationFlags.ProcessIdentityConstraints |
				XmlSchemaValidationFlags.ProcessInlineSchema |
				XmlSchemaValidationFlags.ProcessSchemaLocation;
			settings.ValidationType = ValidationType.Schema;
			settings.XmlResolver = new LocalXmlResolver();

			settings.ValidationEventHandler += ValidationEventHandler;
			settings.Schemas.ValidationEventHandler += ValidationEventHandler;

			ClearValidationLog();
			using (XmlReader validatingReader = XmlReader.Create(xmlStream, settings)) {
				while (validatingReader.Read()) { /* Validate elements */ }
			}
			CheckValidationLog();
		}

		#endregion

		#region XML Document Serialization

		/// <summary>
		/// Serialize an object to a XML stream.
		/// </summary>
		/// <param name="xmlObject">
		/// A <see cref="object"/> that has to be serialized into <paramref name="xmlStream"/>.
		/// </param>
		/// <param name="xmlStream">
		/// A <see cref="Stream"/> that will store the XML document.
		/// </param>
		public static void Serialize(Stream xmlStream, object xmlObject)
		{
			Serialize(xmlStream, xmlObject, null);
		}

		/// <summary>
		/// Serialize an object to a XML stream.
		/// </summary>
		/// <param name="xmlObject">
		/// A <see cref="object"/> that has to be serialized into <paramref name="xmlStream"/>.
		/// </param>
		/// <param name="xmlStream">
		/// A <see cref="Stream"/> that will store the XML document.
		/// </param>
		/// <param name="xmlType">
		/// A <see cref="Type"/> that specify the expected root element. If it null, the type of <paramref name="xmlObject"/>
		/// will be used.
		/// </param>
		public static void Serialize(Stream xmlStream, object xmlObject, Type xmlType)
		{
			XmlSerializer xmlSerializer;

			if (xmlObject == null)
				throw new ArgumentNullException("xmlObject");
			if (xmlStream == null)
				throw new ArgumentNullException("xmlStream");

			// Automatically derive XML root element
			if (xmlType == null)
				xmlType = xmlObject.GetType();

			string xmlSerializerKey = GetXmlSerializerHashKey(xmlType, null);

			// Get the necessary serializer
			if (sXmlSerializers.TryGetValue(xmlSerializerKey, out xmlSerializer) == false) {
				// Create serializer
				xmlSerializer = new XmlSerializer(xmlType, "http://www.derm.altervista.org/Derm.xsd");
				// Cache it for later uses
				sXmlSerializers[xmlSerializerKey] = xmlSerializer;
			}

			// Serialize object into stream
			xmlSerializer.Serialize(xmlStream, xmlObject);
		}

		/// <summary>
		/// Deserialize an object from a XML stream.
		/// </summary>
		/// <param name="xmlStream">
		/// A <see cref="Stream"/> that specify the XML document.
		/// </param>
		/// <param name="xmlType">
		/// A <see cref="Type"/> that specify the expected root element.
		/// </param>
		/// <returns>
		/// It returns an object of type <paramref name="xmlType"/>, which content is determined by <paramref name="xmlStream"/>.
		/// </returns>
		public static object Deserialize(Stream xmlStream, Type xmlType)
		{
			return (Deserialize(xmlStream, xmlType, null));
		}

		/// <summary>
		/// Deserialize an object from a XML stream.
		/// </summary>
		/// <param name="xmlStream">
		/// A <see cref="Stream"/> that specify the XML document.
		/// </param>
		/// <param name="xmlType">
		/// A <see cref="Type"/> that specify the expected root element.
		/// </param>
		/// <param name="xmlTypeNamespace">
		/// A <see cref="System.String"/> that specify the default namespace of XML elements. It can be null for indicating no
		/// default namespace.
		/// </param>
		/// <returns>
		/// It returns an object of type <paramref name="xmlType"/>, which content is determined by <paramref name="xmlStream"/>.
		/// </returns>
		public static object Deserialize(Stream xmlStream, Type xmlType, string xmlTypeNamespace)
		{
			if (xmlStream == null)
				throw new ArgumentNullException("xmlStream");
			if (xmlType == null)
				throw new ArgumentNullException("xmlType");

			XmlSerializer xmlSerializer;
			string xmlSerializerKey = GetXmlSerializerHashKey(xmlType, xmlTypeNamespace);

			// Get the necessary serializer
			if (sXmlSerializers.TryGetValue(xmlSerializerKey, out xmlSerializer) == false) {
				// Create serializer
				xmlSerializer = new XmlSerializer(xmlType, xmlTypeNamespace);
				// Cache it for future uses
				sXmlSerializers[xmlSerializerKey] = xmlSerializer;
			}

			// Deserialize object from stream
			XmlReaderSettings settings = new XmlReaderSettings();

			//settings.ProhibitDtd = false;
			settings.ValidationFlags =
				XmlSchemaValidationFlags.ProcessIdentityConstraints |
				//XmlSchemaValidationFlags.ProcessInlineSchema |
				//XmlSchemaValidationFlags.ProcessSchemaLocation |
				XmlSchemaValidationFlags.ReportValidationWarnings;
			settings.ValidationType = ValidationType.Schema;
			//settings.XmlResolver = new LocalXmlResolver();

//			settings.Schemas.Add("http://www.w3.org/XML/1998/namespace", GetXmlSchemaPath("xml.xsd"));
			//settings.Schemas.Add(xmlTypeNamespace, GetXmlSchemaPath("Collada1.4.xsd"));

			settings.ValidationEventHandler += ValidationEventHandler;
			settings.Schemas.ValidationEventHandler += ValidationEventHandler;

			ClearValidationLog();

			XmlReader xmlReader = XmlReader.Create(xmlStream, settings);

			CheckValidationLog();

			return (xmlSerializer.Deserialize(xmlReader));
		}

		/// <summary>
		/// Compute the key used for caching a XmlSerializer instance.
		/// </summary>
		/// <param name="xmlType"></param>
		/// <param name="defaultNamespace"></param>
		/// <returns></returns>
		private static string GetXmlSerializerHashKey(Type xmlType, string defaultNamespace)
		{
			if (xmlType == null)
				throw new ArgumentNullException("xmlType");

			return ((defaultNamespace != null) ? String.Format("{0}:{1}", defaultNamespace, xmlType.Name) : xmlType.Name);
		}

		/// <summary>
		/// Map between types and XmlSerializer instances (cache).
		/// </summary>
		private static readonly Dictionary<string, XmlSerializer> sXmlSerializers = new Dictionary<string, XmlSerializer>();

		#endregion

		#region Validation Log

		/// <summary>
		/// Handler for being notified about XML validation errors and warnings.
		/// </summary>
		/// <param name="sender">
		/// A <see cref="Object"/> that has raised the event handled.
		/// </param>
		/// <param name="e">
		/// A <see cref="ValidationEventArgs"/> taht describes the validation event.
		/// </param>
		private static void ValidationEventHandler(object sender, ValidationEventArgs e)
		{
			// Populate log
			sValidationLog.AppendFormat("{0}: {1}", e.Message, e.Exception);
			sValidationLog.AppendLine();
			// Signal validation error
			sValidationError = true;
		}

		/// <summary>
		/// Check whether a validation error has occurred.
		/// </summary>
		/// <exception cref="InvalidOperationException">
		/// Exception thrown if there was some validation error.
		/// </exception>
		private static void CheckValidationLog()
		{
			if (sValidationError == true)
				throw new InvalidOperationException(sValidationLog.ToString());
		}

		/// <summary>
		/// Clear the validation log variables.
		/// </summary>
		private static void ClearValidationLog()
		{
			// Reset the error flag
			sValidationError = false;
			// Reset the log text
			sValidationLog.Remove(0, sValidationLog.Length);
		}

		/// <summary>
		/// Flag indicating whether there was a validation error.
		/// </summary>
		private static bool sValidationError;

		/// <summary>
		/// Text that specify validation error information.
		/// </summary>
		private static readonly StringBuilder sValidationLog = new StringBuilder();

		#endregion

		#region Application Namespaces

		/// <summary>
		/// The default namespace for Derm XML documents.
		/// </summary>
		public const string DermNamespace = "http://www.derm.altervista.org/Derm.xsd";

		#endregion

		#region IXmlSerializable Helper Methods

		/// <summary>
		/// Skip the current element.
		/// </summary>
		/// <param name="xmlReader">
		/// A <see cref="XmlReader"/> used for parsing XML document. The instance shall point at the beginning of the XML
		/// element to parse.
		/// </param>
		public static void SkipXmlElement(XmlReader xmlReader)
		{
			if (xmlReader == null)
				throw new ArgumentNullException("xmlReader");

			int depth = xmlReader.Depth;

			xmlReader.ReadStartElement();

			// Skip all children at the current level
            while (xmlReader.Depth > depth)
                    xmlReader.Read();
		}

		/// <summary>
		/// Tipical IXmlSerializable.ReadXml implementation for a generic complex type.
		/// </summary>
		/// <param name="xmlReader">
		/// A <see cref="XmlReader"/> used for parsing XML document. The instance shall point at the beginning of the XML
		/// element to parse.
		/// </param>
		/// <param name="xmlSerializable">
		/// A <see cref="IXmlSerializableExt"/> which shall store information of the current XML element.
		/// </param>
		/// <exception cref="ArgumentNullException">
		/// Exception thrown if <paramref name="xmlReader"/> or <paramref name="xmlSerializable"/> are null.
		/// </exception>
		public static void ReadXml(XmlReader xmlReader, IXmlSerializableExt xmlSerializable)
		{
			if (xmlReader == null)
				throw new ArgumentNullException("xmlReader");
			if (xmlSerializable == null)
				throw new ArgumentNullException("xmlSerializable");

			ReadXmlExt(xmlReader, xmlSerializable, delegate(XmlReader xmlSubReader)
           	{
				// Decode child element
				xmlSerializable.ReadElement(xmlSubReader.LocalName, xmlSubReader);
           	});
		}

		/// <summary>
		/// Tipical IXmlSerializable.ReadXml implementation for elements having only a text content.
		/// </summary>
		/// <param name="xmlReader">
		/// A <see cref="XmlReader"/> used for parsing XML document. The instance shall point at the beginning of the XML
		/// element to parse.
		/// </param>
		/// <returns>
		/// It returns a <see cref="System.String"/> representing the text contained in the XML element.
		/// </returns>
		public static string ReadXmlTextElement(XmlReader xmlReader)
		{
			if (xmlReader == null)
				throw new ArgumentNullException("xmlReader");

			string elementText = String.Empty;
			bool isEmpty = xmlReader.IsEmptyElement;

			// Parse start element (if required)
			if (xmlReader.NodeType == XmlNodeType.Element)
				xmlReader.ReadStartElement();

			if (isEmpty == false)
				elementText = xmlReader.ReadContentAsString();

			return (elementText);
		}

		/// <summary>
		/// Tipical IXmlSerializable.ReadXml implementation for a complex type containing only elements of the same type.
		/// </summary>
		/// <typeparam name="T">
		/// A type that implements a <see cref="IXmlSerializableExt"/> able to parse XML representation.
		/// </typeparam>
		/// <param name="xmlReader">
		/// A <see cref="XmlReader"/> used for parsing XML document. The instance shall point at the beginning of the XML
		/// element to parse.
		/// </param>
		/// <returns>
		/// It returns a list of object of type T, representing the list of children contained in the XML element.
		/// </returns>
		public static List<T> ReadXmlElementList<T>(XmlReader xmlReader) where T : IXmlSerializableExt, new()
		{
			List<T> elementList = new List<T>();

			ReadXmlExt(xmlReader, null, delegate(XmlReader xmlSubReader)
           	{
				T elementObject = new T();

				// Decode child element
				elementObject.ReadXml(xmlSubReader);

				// Collect element
				elementList.Add(elementObject);
           	});

			return (elementList);
		}

		/// <summary>
		/// Tipical IXmlSerializable.ReadXml implementation for a complex type containing only elements subclassing a certain type.
		/// </summary>
		/// <typeparam name="T">
		/// A type that implements a <see cref="IXmlSerializableExt"/> able to parse XML representation.
		/// </typeparam>
		/// <param name="xmlReader">
		/// A <see cref="XmlReader"/> used for parsing XML document. The instance shall point at the beginning of the XML
		/// element to parse.
		/// </param>
		/// <returns>
		/// It returns a list of objects subclassing T, representing the list of children contained in the XML element.
		/// </returns>
		public static List<T> ReadXmlDynamicElementList<T>(XmlReader xmlReader) where T : IXmlSerializableExt
		{
			List<T> elementList = new List<T>();

			ReadXmlExt(xmlReader, null, delegate(XmlReader xmlSubReader)
           	{
				Type elementObjectType = Type.GetType(String.Format("Derm.Scene.{0}", xmlSubReader.LocalName), false, false);
           		T elementObject;

				if (elementObjectType == null)
					throw new InvalidOperationException(String.Format("unknown element {0}", xmlSubReader.LocalName));
				if (elementObjectType.IsAbstract || elementObjectType.IsInterface)
					throw new InvalidOperationException(String.Format("element {0} have no concrete implementation", xmlSubReader.LocalName));
				if (elementObjectType.IsSubclassOf(typeof(T)) == false)
					throw new InvalidOperationException(String.Format("element {0} doesn't inherit from {1}", xmlSubReader.LocalName, typeof(T).FullName));

				// Create object (dynamically)
				elementObject = (T) Activator.CreateInstance(elementObjectType);

           		// Decode child element
				elementObject.ReadXml(xmlSubReader);

				// Collect element
				elementList.Add(elementObject);
           	});

			return (elementList);
		}

		/// <summary>
		/// Tipical IXmlSerializable.ReadXml implementation for a simple type containing only text representaing date and time.
		/// </summary>
		/// <param name="xmlReader">
		/// A <see cref="XmlReader"/> used for parsing XML document. The instance shall point at the beginning of the XML
		/// element to parse.
		/// </param>
		/// <returns>
		/// It returns the date and time represented by the XML element.
		/// </returns>
		public static DateTime ReadXmlDateTime(XmlReader xmlReader)
		{
			if (xmlReader == null)
				throw new ArgumentNullException("xmlReader");

			string contents = XmlHelper.ReadXmlTextElement(xmlReader);

			return (DateTime.Parse(contents));
		}

		/// <summary>
		/// Tipical IXmlSerializable.ReadXml implementation for a simple enumeration type.
		/// </summary>
		/// <typeparam name="T">
		/// The tye of the enumeration.
		/// </typeparam>
		/// <param name="value">
		/// A <see cref="System.String"/> representing the enumeration value of type <typeparamref name="T"/>.
		/// element to parse.
		/// </param>
		/// <returns>
		/// It returns the enumeration value of type <typeparamref name="T"/> corresponding to <paramref name="value"/>.
		/// </returns>
		public static T ReadXmlEnum<T>(string value)
		{
			if (value == null)
				throw new ArgumentNullException("value");

			Type enumType = typeof(T);

			if (enumType.IsEnum == false)
				throw new InvalidOperationException(String.Format("the type {0} is not an enum", enumType));

			MemberInfo[] enumFields = enumType.GetFields();

			foreach (FieldInfo field in enumFields) {
				Attribute enumFieldAttribute = Attribute.GetCustomAttribute(field, typeof(XmlEnumAttribute));
				XmlEnumAttribute enumXmlAttribute = (XmlEnumAttribute)enumFieldAttribute;

				if ((enumXmlAttribute != null) && (enumXmlAttribute.Name == value))
					return ((T)field.GetValue(null));
				if ((enumXmlAttribute == null) && (field.Name == value))
					return ((T)field.GetValue(null));
			}

			throw new ArgumentException("unknown enumeration value " + value);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <typeparam name="T">
		/// The tye of the enumeration.
		/// </typeparam>
		/// <param name="xmlReader">
		/// A <see cref="XmlReader"/> used for parsing XML document. The instance shall point at the beginning of the XML
		/// element to parse.
		/// </param>
		/// <returns>
		/// It returns the enumeration value of type <typeparamref name="T"/> corresponding to <paramref name="value"/>.
		/// </returns>
		public static T ReadXmlEnumElement<T>(XmlReader xmlReader)
		{
			if (xmlReader == null)
				throw new ArgumentNullException("xmlReader");

			return (ReadXmlEnum<T>(ReadXmlTextElement(xmlReader)));
		}

		/// <summary>
		/// Read a list element composed by boolean values. 
		/// </summary>
		/// <param name="xmlReader">
		/// A <see cref="XmlReader"/> used for parsing XML document. The instance shall point at the beginning of the XML
		/// element to parse.
		/// </param>
		/// <returns>
		/// It returns an array of boolean values stored in the element text.
		/// </returns>
		public static bool[] ReadXmlListOfBools(XmlReader xmlReader)
		{
			if (xmlReader == null)
				throw new ArgumentNullException("xmlReader");

			List<bool> list = new List<bool>();
			string contents = ReadXmlTextElement(xmlReader);

			foreach (Match listItem in sBoolRegex.Matches(contents))
				list.Add(Boolean.Parse(listItem.Value));

			return (list.ToArray());
		}

		/// <summary>
		/// Read a list element composed by floating point values.
		/// </summary>
		/// <param name="xmlReader">
		/// A <see cref="XmlReader"/> used for parsing XML document. The instance shall point at the beginning of the XML
		/// element to parse.
		/// </param>
		/// <returns>
		/// It returns an array of floating point values stored in the element text.
		/// </returns>
		public static float[] ReadXmlListOfFloats(XmlReader xmlReader)
		{
			if (xmlReader == null)
				throw new ArgumentNullException("xmlReader");

			List<float> list = new List<float>();
			string contents = ReadXmlTextElement(xmlReader);

			foreach (Match listItem in sFloatRegex.Matches(contents))
				list.Add(Single.Parse(listItem.Value, NumberFormatInfo.InvariantInfo));

			return (list.ToArray());
		}

		/// <summary>
		/// Read a list element composed by unsigned integer values.
		/// </summary>
		/// <param name="xmlReader">
		/// A <see cref="XmlReader"/> used for parsing XML document. The instance shall point at the beginning of the XML
		/// element to parse.
		/// </param>
		/// <returns>
		/// It returns an array of floating point values stored in the element text.
		/// </returns>
		public static uint[] ReadXmlListOfUInts(XmlReader xmlReader)
		{
			if (xmlReader == null)
				throw new ArgumentNullException("xmlReader");

			List<uint> list = new List<uint>();
			string contents = ReadXmlTextElement(xmlReader);

			foreach (Match listItem in sFloatRegex.Matches(contents))
				list.Add(UInt32.Parse(listItem.Value, NumberFormatInfo.InvariantInfo));

			return (list.ToArray());
		}

		/// <summary>
		/// Delegate used for parsing XML element children.
		/// </summary>
		/// <param name="xmlReader">
		/// A <see cref="XmlReader"/> used for parsing XML document.
		/// </param>
		private delegate void ReadElementDelegate(XmlReader xmlReader);

		/// <summary>
		/// Tipical IXmlSerializable.ReadXml implementation.
		/// </summary>
		/// <param name="xmlReader">
		/// A <see cref="XmlReader"/> used for parsing XML document. The instance shall point at the beginning of the XML
		/// element to parse.
		/// </param>
		/// <param name="xmlSerializable">
		/// A <see cref="IXmlSerializableExt"/> which shall store information of the current XML element. It can be null if it
		/// is not applicable.
		/// </param>
		/// <param name="readElementCallback">
		/// A <see cref="ReadElementDelegate"/> used for customization of the children element parsing.
		/// </param>
		/// <exception cref="ArgumentNullException">
		/// Exception thrown if <paramref name="xmlReader"/> or <paramref name="readElementCallback"/> are null.
		/// </exception>
		private static void ReadXmlExt(XmlReader xmlReader, IXmlSerializableExt xmlSerializable, ReadElementDelegate readElementCallback)
		{
			bool isEmpty;

			if (xmlReader == null)
				throw new ArgumentNullException("xmlReader");
			if (readElementCallback == null)
				throw new ArgumentNullException("readElementCallback");

			// Empty element?
			isEmpty = xmlReader.IsEmptyElement;
			// Decode attributes
			if ((xmlReader.HasAttributes == true) && (xmlSerializable != null))
				xmlSerializable.ReadAttributes(xmlReader);

			// Read the root start element
			xmlReader.ReadStartElement();

			// Decode elements
			if (isEmpty == false) {
				do {
					// Read document till next element
					xmlReader.MoveToContent();

					if (xmlReader.NodeType == XmlNodeType.Element) {
						string elementName = xmlReader.LocalName;

						// Empty element?
						isEmpty = xmlReader.IsEmptyElement;

						// Decode child element
						readElementCallback(xmlReader);
						xmlReader.MoveToContent();

						// Read the child end element (not empty)
						if (isEmpty == false) {
							// Delegate check: it has to reach and end element
							if (xmlReader.NodeType != XmlNodeType.EndElement)
								throw new InvalidOperationException(String.Format("not reached the end element"));
							// Delegate check: the end element shall correspond to the start element before delegate
							if (xmlReader.LocalName != elementName)
								throw new InvalidOperationException(String.Format("not reached the relative end element of {0}", elementName));

							// Child end element
							xmlReader.ReadEndElement();
						}
					} else if (xmlReader.NodeType == XmlNodeType.Text) {
						if (xmlSerializable != null) {
							// Interface
							xmlSerializable.ReadText(xmlReader);
							Debug.Assert(xmlReader.NodeType != XmlNodeType.Text, "IXmlSerializableExt.ReadText shall read the text");
						} else
							xmlReader.Skip();	// Skip text
					}
				} while (xmlReader.NodeType != XmlNodeType.EndElement);
			}
		}

		/// <summary>
		/// Regular expression for matching a unsigned integer number.
		/// </summary>
		private static readonly Regex sBoolRegex = new Regex("(1|0|true|false)", RegexOptions.Compiled);

		/// <summary>
		/// Regular expression for matching a floating point number.
		/// </summary>
		private static readonly Regex sFloatRegex = new Regex(@"[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?", RegexOptions.Compiled);

		/// <summary>
		/// Regular expression for matching a integer number.
		/// </summary>
		private static readonly Regex sIntRegex = new Regex("[0-9]+", RegexOptions.Compiled);

		/// <summary>
		/// Regular expression for matching a unsigned integer number.
		/// </summary>
		private static readonly Regex sUnsignedIntRegex = new Regex("[0-9]+", RegexOptions.Compiled);

		#endregion

		#region XML Serialization Reflection Helper Methods

		/// <summary>
		/// Read XML attributes of an object by means of reflection.
		/// </summary>
		/// <param name="xmlReader">
		/// A <see cref="XmlReader"/> used for parsing XML document. The instance shall point to the element to parse.
		/// </param>
		/// <param name="xmlInstance">
		/// A <see cref="System.Object"/> which stores attribute information of the current element of <paramref name="xmlReader"/>.
		/// </param>
		/// <remarks>
		/// <para>
		/// This method uses reflection to read available attributes of <paramref name="xmlInstance"/>, and try to find them using
		/// <paramref name="xmlReader"/>.
		/// </para>
		/// <para>
		/// The attributes are identified by getting the <see cref="XmlAttributeAttribute"/> custom attribute associated with the
		/// memebers. Candidates members are properties and field, and they must met the following conditions:
		/// - Properties and fields can have any protection level (both public and non-public members are queried.
		/// - Properties and fields must have a setter method.
		/// - Properties cannot be indexed.
		/// </para>
		/// </remarks>
		public static void ReadXmlAttributes(XmlReader xmlReader, object xmlInstance)
		{
			if (xmlReader == null)
				throw new ArgumentNullException("xmlReader");
			if (xmlInstance == null)
				throw new ArgumentNullException("xmlInstance");

			Type xmlInstanceType = xmlInstance.GetType();

			BindingFlags xmlInstanceReflectionFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly;
			FieldInfo[] xmlInstanceFields = xmlInstanceType.GetFields(xmlInstanceReflectionFlags | BindingFlags.SetField);
			PropertyInfo[] xmlInstanceProperties = xmlInstanceType.GetProperties(xmlInstanceReflectionFlags | BindingFlags.SetProperty);

			// Read attributes stored in fields
			foreach (FieldInfo fieldInfo in xmlInstanceFields) {
				foreach (Attribute attribute in Attribute.GetCustomAttributes(fieldInfo, typeof(XmlAttributeAttribute))) {
					XmlAttributeAttribute xmlAttribute = (XmlAttributeAttribute)attribute;

					if (xmlReader[xmlAttribute.AttributeName] != null) {
						fieldInfo.SetValue(xmlInstance, ConvertXmlValue(xmlReader[xmlAttribute.AttributeName], fieldInfo.FieldType));
						break;
					}
				}
			}

			// Read attributes stored in fields
			foreach (PropertyInfo propertyInfo in xmlInstanceProperties) {

				// Do not consider indexed properties
				if (propertyInfo.GetIndexParameters().Length > 0)
					continue;

				foreach (Attribute attribute in Attribute.GetCustomAttributes(propertyInfo, typeof(XmlAttributeAttribute))) {
					XmlAttributeAttribute xmlAttribute = (XmlAttributeAttribute)attribute;

					if (xmlReader[xmlAttribute.AttributeName] != null) {
						propertyInfo.SetValue(xmlInstance, ConvertXmlValue(xmlReader[xmlAttribute.AttributeName], propertyInfo.PropertyType), null);
						break;
					}
				}
			}
		}

		/// <summary>
		/// Convert a string into a convertible type.
		/// </summary>
		/// <param name="xmlValue"></param>
		/// <param name="xmlValueType"></param>
		/// <returns></returns>
		private static object ConvertXmlValue(string xmlValue, Type xmlValueType)
		{
			if (xmlValue == null)
				throw new ArgumentNullException("xmlValue");
			if (xmlValueType == null)
				throw new ArgumentNullException("xmlValueType");

			// The 'xmlValueType' shall implement IConvertible interface!

			return (Convert.ChangeType(xmlValue, xmlValueType));
		}

		#endregion
	}
}
